package com.triplelink.builder.query;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.openrdf.model.Value;

import com.triplelink.Uris;
import com.triplelink.annotations.DatatypeProperty;
import com.triplelink.context.Connector;
import com.triplelink.context.UserConnector;

public class QueryTL extends UserConnector
{
	private StringBuilder sb = new StringBuilder();
	private Map<String, Value> bindings = new HashMap<String, Value>();
	private Map<String, String> labels = new HashMap<String, String>();
	private Counter counter = new Counter();
	private String tab = "";

	private static final String LABEL_PRIFIX = "tl_label_";
	
	private Map<Class<?>, Set<String>> discoveredIds = new HashMap<Class<?>, Set<String>>();
	
	private QueryTL(Connector connector)
	{
		super(connector);
	}
	
	public QueryTL shareResources()
	{
		QueryTL newQTL = new QueryTL(getConnector());
		
		newQTL.bindings = bindings;
		newQTL.labels = labels;
		newQTL.counter = counter;
		
		return newQTL;
	}
	
	public QueryTL clone()
	{
		QueryTL newQTL = new QueryTL(getConnector());
		
		newQTL.bindings = new HashMap<String, Value>(bindings);
		newQTL.labels = new HashMap<String, String>(labels);
		newQTL.counter = new Counter(counter);
		newQTL.sb = new StringBuilder().append(sb);
		
		return newQTL;
	}

	public QueryTL append(String text)
	{
		sb.append(text);
		
		return this;
	}

	public QueryTL append(StringBuilder text)
	{
		sb.append(text);
		
		return this;
	}

	public QueryTL prepend(String text)
	{
		sb.insert(0, text);
		
		return this;
	}

	public QueryTL prepend(StringBuilder text)
	{
		sb.insert(0, text);
		
		return this;
	}
	
	public QueryTL appendUri(String uri)
	{
		sb
		.append("?").append(getLabel(uri)).append(" ");
		
		return this;
	}
	
	public QueryTL appendLabel(String label)
	{
		sb
		.append("?").append(label).append(" ");
		
		return this;
	}
	
	public String appendRandowLabel()
	{
		String label = createNewLabel();
		
		sb
		.append("?").append(label).append(" ");
		
		return label;
	}
	
	public QueryTL appendEndPoint()
	{
		sb.append(" . \n");
		
		return this;
	}
	
	public QueryTL appendInstanceType(String uri, String classType)
	{
		sb
		.append(getTab())
		.append("?").append(getLabel(uri)).append(" ")
		.append("<").append(Uris.TYPE).append("> ")
		.append("<").append(classType).append("> ").append(" . \n");
		
		return this;
	}
	
	public QueryTL appendLiteral(String uri, String property, String value, DatatypeProperty datatypeProperty)
	{
		sb
		.append(getTab())
		.append("?").append(getLabel(uri)).append(" ")
		.append("<").append(property).append("> ")
		.append("?").append(getLabel(value, datatypeProperty)).append(" . \n");
		
		return this;
	}
	
	public QueryTL appendUri(String uriSubject, String property, String uriObject)
	{
		sb
		.append(getTab())
		.append("?").append(getLabel(uriSubject)).append(" ")
		.append("<").append(property).append("> ")
		.append("?").append(getLabel(uriObject)).append(" . \n");
		
		return this;
	}
	
	public String getLabel(String uri)
	{
		String label = labels.get(uri);
		
		if (label == null)
		{
			label = createNewLabel();
			saveLabel(label, uri);
		}
		
		return label;
	}
	
	private String getLabel(String value, DatatypeProperty datatypeProperty)
	{
		String label = labels.get(value);
		
		if (label == null)
		{
			label = createNewLabel();
			saveLabel(label, value, datatypeProperty);
		}
		
		return label;
	}
	
	public QueryTL saveLabel(String label, String uri)
	{
		labels.put(uri, label);
		bindings.put(label, createURI(uri));
		
		return this;
	}
	
	public QueryTL saveLabel(String label, String value, DatatypeProperty datatypeProperty)
	{
		labels.put(value, label);
		bindings.put(label, createLiteral(value, datatypeProperty));
		
		return this;
	}
	
	public QueryTL appendValuesConstraint(String label, List<String> valuesOpion)
	{
		this
		.appendTab()
		.append("VALUES ")
		.appendLabel(label)
		.append("\n")
		.appendTab()
		.append("{")
		.plusTab()
		.appendTab();
		
		for (String valueOption : valuesOpion)
		{
			appendUri(valueOption);
		}
		
		decreaseTab()
		.appendTab()
		.append("}");
		
		return this;
	}
	
	public static QueryTL create(Connector connector)
	{
		return new QueryTL(connector);
	}

	public StringBuilder getSb() {
		return sb;
	}

	public Map<String, Value> getBindings() {
		return bindings;
	}
	
	public String createNewLabel()
	{
		return LABEL_PRIFIX + counter.next();
	}
	
	public String getTab() {
		return tab;
	}

	public QueryTL setTab(String tab) {
		this.tab = tab;
		
		return this;
	}
	
	public QueryTL plusTab()
	{
		this.tab = this.tab + "\t";
		
		return this;
	}
	
	public QueryTL decreaseTab()
	{
		this.tab = this.tab.substring(1);
		
		return this;
	}
	
	public QueryTL appendTab()
	{
		sb.append(getTab());
		
		return this;
	}
	public String toString()
	{
		return sb.toString();
	}
	
	public boolean hasDicovered(String id, Object entity)
	{
		Set<String> setIds = discoveredIds.get(entity.getClass());
		
		if (setIds == null)
			return false;
		
		return setIds.contains(id);
	}
	
	public void informDiscovered(String id, Object entity)
	{
		Set<String> setIds = discoveredIds.get(entity.getClass());
		
		if (setIds == null)
		{
			setIds = new HashSet<String>();
			discoveredIds.put(entity.getClass(), setIds);
		}
		
		setIds.add(id);
	}
}
